package com.demo.spring.framework.servlet;

import com.demo.spring.framework.annotation.*;
import com.sun.xml.internal.bind.v2.schemagen.xmlschema.Annotated;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class GPDispatcherServlet extends HttpServlet {

    //保存application.properties配置文件中的内容
    private Properties contextConfig = new Properties();

    //保存扫描的所有的类名
    private List<String> classNames = new ArrayList<String>();

    //传说中的IOC容器，我们来揭开它的神秘面纱
    //为了简化程序，暂时不考虑ConcurrentHashMap
    // 主要还是关注设计思想和原理
    private Map<String,Object> ioc = new HashMap<String,Object>();

    //保存url和Method的对应关系
    //private Map<String,Method> handlerMapping = new HashMap<String,Method>();
    private List<Handler> handlerMapping = new ArrayList<Handler>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req,resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 Exection,Detail : " + Arrays.toString(e.getStackTrace()));
        }
    }


    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        Handler handler = getHandler(req);

        if(handler == null){
            resp.getWriter().write("404");
            return;
        }

        Class<?> [] paramTypes = handler.getParamTypes();

        Map<String,String[]> parames = req.getParameterMap();

        Object [] paramValues = new Object[paramTypes.length];
        for(Map.Entry<String,String[]> param: parames.entrySet()){
            if(!handler.paramIndexMapping.containsKey(param.getKey())){continue;}
            String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]","").replaceAll("\\s",",");
            int index = handler.paramIndexMapping.get(param.getKey());

            paramValues[index] = convert(paramTypes[index],value);


        }
        if(handler.paramIndexMapping.containsKey(HttpServletRequest.class.getName())){
            int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[reqIndex] = req;
        }

        if(handler.paramIndexMapping.containsKey(HttpServletResponse.class.getName())){
            int reqIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[reqIndex] = req;
        }

        Object returnValue = handler.method.invoke(handler.controller,paramValues);
        if(returnValue == null || returnValue instanceof Void){ return; }
        resp.getWriter().write(returnValue.toString());


    }

    private Handler getHandler(HttpServletRequest req) {
        String url = req.getRequestURI().replaceAll(req.getContextPath(),"").replaceAll("/+","/");

        for (Handler handler : this.handlerMapping) {
            if(handler.pattern.matcher(url).matches()){
                return handler;
            }
        }
        return null;

    }

    //url传过来的参数都是String类型的，HTTP是基于字符串协议
    //只需要把String转换为任意类型就好
    private Object convert(Class<?> type,String value){
        //如果是int
        if(Integer.class == type){
            return Integer.valueOf(value);
        }
        else if(Double.class == type){
            return Double.valueOf(value);
        }
        //如果还有double或者其他类型，继续加if
        //这时候，我们应该想到策略模式了
        //在这里暂时不实现，希望小伙伴自己来实现
        return value;
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1加载文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        //2扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));

        //3初始化扫描到的类，并且将他们放入IOC容器中
        doInstance();

        //4完成依赖注入
        doAutowired();

        //5初始化HandlerMapping
        initHandlerMapping();

        System.out.println("GP Spring framework is init.");
    }

    private void initHandlerMapping() {
        if(ioc.isEmpty()){return;}

        for(Map.Entry<String,Object> entry : ioc.entrySet()){
            Class<?> clazz = entry.getValue().getClass();

            if(!clazz.isAnnotationPresent(GPController.class)){continue;}

            String baseUrl = "";
            if(clazz.isAnnotationPresent(GPRequestMapping.class)){
                GPRequestMapping requestMapping = clazz.getAnnotation(GPRequestMapping.class);
                baseUrl = requestMapping.value();
            }
            for(Method method:clazz.getMethods()){
                if(method.isAnnotationPresent(GPRequestMapping.class)){
                    GPRequestMapping requestMapping = method.getAnnotation(GPRequestMapping.class);
                    String regex = ("/" + baseUrl + "/" + requestMapping.value())
                            .replaceAll("/+","/");
                    Pattern pattern = Pattern.compile(regex);

                    this.handlerMapping.add(new Handler(pattern,entry.getValue(),method));
                   // handlerMapping.put(url,method);
                    System.out.println("mapped:"+pattern +","+method);
                }
            }
        }
    }

    private void doAutowired() {
        if(ioc.isEmpty()){return;}
        for(Map.Entry<String,Object> entry : ioc.entrySet()){
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for(Field field:fields){
                if(!field.isAnnotationPresent(GPAutowired.class)){continue;}

                GPAutowired autowired = field.getAnnotation(GPAutowired.class);

                String beanName = autowired.value().trim();
                if("".equals(beanName)){
                    beanName = field.getType().getName();
                }
                beanName = toUpCase(beanName);


                //当isAccessible()的结果是false时不允许通过反射访问该字段
                //当该字段时private修饰时isAccessible()得到的值是false，必须要改成true才可以访问
                field.setAccessible(true);
                try {
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private String toUpCase(String beanName) {
        char [] first = beanName.toCharArray();
        if(Character.isLowerCase(first[0])){
            first[0] -=32;
        }
        return String.valueOf(first);
    }


    /**
     * 初始化类
     */
    private void doInstance() {
        if(classNames.isEmpty()){return;}
        for(String className : classNames){
            try {
                Class<?> clazz = Class.forName(className);

                if(clazz.isAnnotationPresent(GPController.class)){
                    Object instance = clazz.newInstance();
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName,instance);
                }else if(clazz.isAnnotationPresent(GPService.class)){
                    GPService service = clazz.getAnnotation(GPService.class);
                    String beanName = service.value();
                    if("".equals(beanName.trim())){
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }
                    Object instance = clazz.newInstance();

                    ioc.put(beanName,instance);

                    for(Class<?> i : clazz.getInterfaces()){
                        if(ioc.containsKey(i.getName())){
                            throw new Exception("已存在");
                        }
                        ioc.put(i.getName(),instance);
                    }
                }else{
                    continue;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] +=32;
        return String.valueOf(chars);
    }

    /**
     * 扫描相关的类
     * @param scanPackage
     */
    private void doScanner(String scanPackage) {
        //scanPackage = com.gupaoedu.demo ，存储的是包路径
        //转换为文件路径，实际上就是把.替换为/就OK了
        //classpath
        URL url = this.getClass().getClassLoader().getResource("/"+scanPackage.replaceAll("\\.","/"));
        File classPath = new File(url.getFile());
        for(File file:classPath.listFiles())
        if(file.isDirectory()){
            doScanner(scanPackage+"."+file.getName());
        }else{
            if(!file.getName().endsWith(".class")){
                continue;
            }
            String className = scanPackage + "." + file.getName().replace(".class","");
            classNames.add(className);
        }

    }

    /**
     * 加载application.propreties
     * @param contextConfigLocation
     */
    private void doLoadConfig(String contextConfigLocation) {
        //直接从类路径下找到Spring主配置文件所在的路径
        //并且将其读取出来放到Properties对象中
        //相对于scanPackage=com.gupaoedu.demo 从文件中保存到了内存中
        InputStream ins = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextConfig.load(ins);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null != ins){
                try {
                    ins.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    class Handler{
        private Pattern pattern;  //正则
        private Method method;
        private Object controller;
        private Class<?> [] paramTypes;
        //形参列表
        //参数的名字作为key,参数的顺序，位置作为值
        private Map<String,Integer> paramIndexMapping;

        public Pattern getPattern() {
            return pattern;
        }

        public Method getMethod() {
            return method;
        }

        public Object getController() {
            return controller;
        }

        public Class<?>[] getParamTypes() {
            return paramTypes;
        }



        public Handler(Pattern pattern,Object controller, Method method){
            this.pattern = pattern;
            this.controller = controller;
            this.method = method;
            paramTypes = method.getParameterTypes();

            paramIndexMapping = new HashMap<String, Integer>();
            putParamIndexMapping(method);

        }

        private void putParamIndexMapping(Method method) {

            Annotation[] [] pa =  method.getParameterAnnotations();

            for(int i = 0; i<pa.length; i++){
                for(Annotation a : pa[i]){
                    if(a instanceof GPRequestParam){
                        String paramName = ((GPRequestParam)a).value();
                        if(!"".equals(paramName.trim())){
                            paramIndexMapping.put(paramName,i);
                        }
                    }
                }
            }
            Class<?> [] paramsTypes = method.getParameterTypes();

            for (int i = 0; i < paramsTypes.length ; i ++) {
                Class<?> type = paramsTypes[i];
                if(type == HttpServletRequest.class ||
                        type == HttpServletResponse.class){
                    paramIndexMapping.put(type.getName(),i);
                }
            }

        }
    }


}
